// ThreadSafeProxy
//
// This sample custom real proxy provides single-threaded access to the
// target object the proxy is wrapped around.  Two mechanisms are supported
// for installing the proxy:
//
// o Calling ThreadSafeProxy.Wrap on any MarshalByRefObject-derived object
//   reference.
//
// o Applying the ThreadSafeProxyAttribute to any ContextBoundObject-derived
//   type.
//
// These two techniques are mutually exclusive of one another.  Refer to app.cs
// for sample usage.
//
// Calls through the proxy to the target object will be serialized on a simple
// SyncBlock.  For scenarios where external synchronization is required (for example,
// holding a lock while iterating over multiple calls to the target object),
// the real proxy provides an implementation of ISynchronize (defined below)
// that allows a reference to the internal lock used by the proxy to be acquired.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Services;
using System.Reflection;

// The custom real proxy implements the following interface
// to support 'external synchronization' outside of the
// target object -- for scenarios where multiple calls to the
// target object need to be protected with a lock.
// Refer to app.cs for an example.
// 
namespace PaulRz.Remoting
{

	public interface ISynchronize
	{
		object SyncRoot { get; }
	}

	// ThreadSafeProxyAttribute
	//
	// When applied to ContextBoundObject types, installs the
	// ThreadSafeProxy custom proxy when operator new is used
	// on the specified type.  Alternatively, the
	// static ThreadSafeProxy.Wrap method may be used to "wrap"
	// an instance of the ThreadSafeProxy around an existing
	// object reference.
	//
	[AttributeUsage(AttributeTargets.Class)]
	public sealed class ThreadSafeProxyAttribute : ProxyAttribute
	{
		public override MarshalByRefObject CreateInstance(Type serverType)
		{
			MarshalByRefObject target = base.CreateInstance(serverType);
			ThreadSafeProxy proxy = new ThreadSafeProxy(serverType, target);
			MarshalByRefObject tp = (MarshalByRefObject)proxy.GetTransparentProxy();
			return(tp);
		}        
	}

	// ThreadSafeProxy
	//
	// Custom proxy that protects all member accesses (field, method, property, etc)
	// on a target object with a lock.  Also provides an implementation of the
	// ISynchronize interface (defined above), which allows for external code constructs
	// to acquire the same lock that the proxy uses (in order to provide outer-scope
	// locking - such as around iteration over the target type, or other multi-member
	// accesses).
	//
	public class ThreadSafeProxy : RealProxy, IRemotingTypeInfo
	{
		// Wrap
		//
		// This method can be used with MarshalByRefObjectd types that do not
		// have the custom ThreadSafeProxy attribute applied.
		//
		public static object Wrap( MarshalByRefObject o )
		{
			if( o.GetType().IsDefined(typeof(ThreadSafeProxyAttribute), true) )
			{
				throw new ApplicationException( "ThreadSafeProxy.Wrap cannot be used on objects " +
					"that have the ThreadSafeProxyAttribute applied. " +
					"Either remove the ThreadSafeProxyAttribute, or use " +
					"operator new to instantiate." );
			}

			if( RemotingServices.IsTransparentProxy(o) &&
				(RemotingServices.GetRealProxy(o).GetType() == typeof(ThreadSafeProxy)) )
			{
				throw new ApplicationException("Target object has already been wrapped with a ThreadSafeProxy.");
			}

			ThreadSafeProxy proxy = new ThreadSafeProxy(o);
			return proxy.GetTransparentProxy();
		}

		public ThreadSafeProxy( MarshalByRefObject targetObj )
			: this(targetObj.GetType(), targetObj)
		{
			// Initialize the sink chain once so that the runtime doesn't have to
			// do this during every method invocation (resulting in a measurable
			// perf hit).
			//
			ObjRef oRef = RemotingServices.Marshal(targetObj);
			RemotingServices.Unmarshal(oRef);
		}

		public ThreadSafeProxy( Type targetType, MarshalByRefObject targetObj )
			: base(targetType)
		{
			this.targetObj = targetObj;
		}

		public override IMessage Invoke( IMessage msg )
		{
			if( msg is IConstructionCallMessage )
			{
				IConstructionCallMessage ccm = (IConstructionCallMessage)msg;

				// Construct the real server object in the _target
				object o = RemotingServices.GetRealProxy(targetObj).InitializeServerObject(ccm);

				// Since we are circumventing remoting's default activation
				// steps we need to ensure that the "_target" has its
				// channel set up appropriately .. these 2 calls do that.
				ObjRef oRef = RemotingServices.Marshal(targetObj);
				RemotingServices.Unmarshal(oRef);

				// Create a return message of our own carrying our proxy
				// as the returned initialized proxy
				MarshalByRefObject tp = (MarshalByRefObject)this.GetTransparentProxy();
				return EnterpriseServicesHelper.CreateConstructionReturnMessage(ccm, tp);
			}

			IMethodCallMessage msgCall = (IMethodCallMessage)msg;

			// Handle ISynchronize.SyncRoot
			//
			if( msgCall.MethodBase == methodBaseISynchronizeSyncRootGetter ) 
			{
				return new ReturnMessage(lockObj, null, 0, null, msgCall);
			}

			// Delegate to target object while holding its lock.
			//
			lock( lockObj )
			{
				return RemotingServices.ExecuteMessage(targetObj, msgCall);
			}
		}

		// IRemotingTypeInfo
		//
		public bool CanCastTo( Type targetType, object targetObject )
		{
			// Handle casts to ISynchronize.
			//
			if( targetType == typeof(ISynchronize) )
			{
				return(true);
			}

			// Delegate to target object.
			//
			return targetType.IsAssignableFrom(targetObject.GetType());
		}

		public string TypeName
		{
			get { return GetProxiedType().ToString(); }
			set { throw new NotSupportedException(); }
		}

		// lockObj
		//   This is the object who's lock we'll use to synchronize
		//   member access on the target object.
		//
		private object lockObj = new object();

		// targetObj
		//   The actual target object we're wrapping.
		//
		private MarshalByRefObject targetObj;

		// methodBaseISynchronizeSyncRootGetter
		//   This is the cached MethodBase for the getter of the ISynchronize.SyncRoot
		//   property, which is used in Invoke to detect calls to this property.
		//
		private static MethodBase methodBaseISynchronizeSyncRootGetter =
			typeof(ISynchronize).GetProperty("SyncRoot").GetAccessors()[0];

	}
}